﻿using PerpetuumSoft.Knockout;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace OperationPlugins.WebApp.Views
{
    public static class HtmlExtensions
    {

        public static HtmlString Init<TModel>(this KnockoutContext<TModel> context, TModel model, string wrapperId = "", bool applyOnDocumentReady = false)
        {
            string script = context.Apply(model, wrapperId, applyOnDocumentReady).ToHtmlString();
            int position = script.LastIndexOf("ko.applyBindings(viewModel);", StringComparison.InvariantCulture);
            script = script.Insert(position, "ko.watch(viewModel, { depth: -1 }, function(parents, child) { hitChangeTracker(viewModel, child()); });" + Environment.NewLine);
            return new HtmlString(script);
        }

        public static KnockoutBinding<TModel> ButtonWithImage<TModel>(this KnockoutHtml<TModel> html, string text, string controllerName, string actionName, string image, object htmlAttributes = null)
        {
            var binding = html.Button(text, actionName, controllerName, null, htmlAttributes);
            if (image != null)
            {
                binding.InnerHtml = string.Format("<i class=\"{0}\"></i> &nbsp;&nbsp; {1}", image, text);
            }
            return binding;
        }

        public static KnockoutBinding<TModel> Td<TModel>(this KnockoutHtml<TModel> html, Expression<Func<TModel, object>> text, KnockoutContext<TModel> context)
        {
            return new MyParentKnockoutBinding<TModel>(context, html.Span(text)) { TagName = "td" };
        }

        public static KnockoutBinding<TModel> Td<TModel>(this KnockoutHtml<TModel> html, string text, string controllerName, string actionName, string imageName, KnockoutContext<TModel> context, object routeValues, object htmlAttributes = null)
        {
            var binding = html.Button(text, actionName, controllerName, routeValues, htmlAttributes);
            binding.InnerHtml = string.Format("<i class=\"{0}\" title=\"{1}\"></i>{2}", imageName, text, "");
            return new MyParentKnockoutBinding<TModel>(context, binding) { TagName = "td" };
        }

        public static KnockoutBinding<TModel> TrBegin<TModel>(this KnockoutHtml<TModel> html, KnockoutContext<TModel> context)
        {
            return new MyKnockoutBinding<TModel>(context) { TagName = "tr", WriteStartTag = true, WriteEndTag = false };
        }

        public static KnockoutBinding<TModel> TrEnd<TModel>(this KnockoutHtml<TModel> html, KnockoutContext<TModel> context)
        {
            return new MyKnockoutBinding<TModel>(context) { TagName = "tr", WriteStartTag = false, WriteEndTag = true };
        }

        private sealed class MyParentKnockoutBinding<TModel> : KnockoutBinding<TModel>
        {
            private readonly KnockoutTagBuilder<TModel> _tagBuilder;

            public string TagName { get; set; }

            public MyParentKnockoutBinding(KnockoutContext<TModel> context, KnockoutTagBuilder<TModel> tagBuilder)
                : base(context, null, null)
            {
                _tagBuilder = tagBuilder;
            }

            public override string ToHtmlString()
            {

                foreach (var item in Items)
                {
                    _tagBuilder.Items.Add(item);
                }

                string inner = _tagBuilder.ToHtmlString();

                string s = string.Format("<{0}>{1}</{2}>", TagName, inner, TagName);
                return s;
            }
        }

        private sealed class MyKnockoutBinding<TModel> : KnockoutBinding<TModel>
        {
            public MyKnockoutBinding(KnockoutContext<TModel> context)
                : base(context, null, null)
            {
            }

            public string TagName { get; set; }
            public bool WriteStartTag { get; set; }
            public bool WriteEndTag { get; set; }
            public string HtmlStyles { get; set; }

            public override string ToHtmlString()
            {
                var s = new StringBuilder();

                if (WriteStartTag)
                {
                    var middle = new StringBuilder();

                    if (string.IsNullOrEmpty(HtmlStyles) == false)
                    {
                        middle.Append("style=\"");
                        middle.Append(HtmlStyles);
                        middle.Append("\" ");
                    }

                    var html = base.ToHtmlString();
                    middle.Append(html);

                    s.Append(string.Format("<{0} {1}>", TagName, middle));
                }

                if (WriteEndTag)
                {
                    s.Append(string.Format("</{0}>", TagName));
                }

                return s.ToString();
            }
        }



        public static KnockoutBinding<TModel> Event<TModel>(this KnockoutBinding<TModel> knockoutBinding, string eventName, string action, string controller)
        {
            return Event(knockoutBinding, eventName, action, controller, null, null);
        }

        public static KnockoutBinding<TModel> Event<TModel>(this KnockoutBinding<TModel> knockoutBinding, string eventName, string action, string controller, string parameter, Expression<Func<TModel, object>> urlParametersGetter)
        {
            var item = new ServerActionKnockoutBindingItem
            {
                Name = eventName,
                UrlExpression = data => GetServerUrlExpression(data, action, controller, parameter, urlParametersGetter)
            };

            knockoutBinding.Items.Add(item);
            return knockoutBinding;
        }

        private class EvaluatedValueKnockoutBindingItem : KnockoutBindingItem
        {
            public Func<KnockoutExpressionData, string> ValueExpression { get; set; }

            public override string GetKnockoutExpression(KnockoutExpressionData data)
            {
                if (ValueExpression == null)
                    throw new InvalidOperationException("Value expression is miss");

                var stringBuilder = new StringBuilder();
                stringBuilder.Append(Name);
                stringBuilder.Append(" : ");
                stringBuilder.Append(ValueExpression(data));
                return stringBuilder.ToString();
            }
        }

        private class ServerActionKnockoutBindingItem : KnockoutBindingItem
        {
            public Func<KnockoutExpressionData, string> UrlExpression { get; set; }

            public sealed override string GetKnockoutExpression(KnockoutExpressionData data)
            {
                if (UrlExpression == null)
                    throw new InvalidOperationException("Url expression is miss");

                var stringBuilder = new StringBuilder();
                stringBuilder.Append(Name);
                stringBuilder.Append(" : ");
                stringBuilder.Append("function() {");
                stringBuilder.AppendFormat("executeOnServer($root, {0})", UrlExpression(data));
                stringBuilder.Append(";}");
                return stringBuilder.ToString();
            }
        }

        private static string GetUrl(string action, string controller, string parameterName, string parameterValueScript)
        {
            const string parameterPlaceholder = "__A5B7A8749D6__";
            var url = GetUrlHelper().Action(action, controller, new RouteValueDictionary { { parameterName, parameterPlaceholder } });
            return url.EndsWith(parameterPlaceholder)
                       ? "'" + url.Replace(parameterPlaceholder, "' + " + parameterValueScript)
                       : "'" + url.Replace(parameterPlaceholder, "' + " + parameterPlaceholder + " + '");
        }

        private static UrlHelper GetUrlHelper()
        {
            return new UrlHelper(new System.Web.Routing.RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData()));
        }

        private static string GetServerUrlExpression<TModel>(KnockoutExpressionData data, Expression<Func<TModel, string>> urlGetter)
        {
            var expression = KnockoutExpressionConverter.Convert(urlGetter, data);
            if (string.IsNullOrWhiteSpace(expression))
            {
                throw new InvalidOperationException("Url getter is invalid");
            }
            return expression;
        }

        private static string GetServerUrlExpression<TModel>(KnockoutExpressionData data, string action, string controller, string parameterName, Expression<Func<TModel, object>> parametersGetter)
        {
            if (!string.IsNullOrWhiteSpace(parameterName) && parametersGetter == null)
                throw new InvalidOperationException("Url parameter getter is miss");

            string expression;
            if (parametersGetter == null)
            {
                expression = "'" + GetUrlHelper().Action(action, controller) + "'";
            }
            else
            {
                var getterExpression = KnockoutExpressionConverter.Convert(parametersGetter, data);
                if (string.IsNullOrWhiteSpace(getterExpression))
                {
                    throw new InvalidOperationException("Url parameter getter is invalid");
                }

                if (parametersGetter.Body.NodeType == ExpressionType.MemberAccess)
                {
                    getterExpression += "()";
                }
                expression = GetUrl(action, controller, parameterName, getterExpression);
            }
            return expression;
        }




    }
}